package com.duomn.mybatis.plugins.tools;

import static org.springframework.jdbc.datasource.DataSourceUtils.isConnectionTransactional;
import static org.springframework.jdbc.datasource.DataSourceUtils.releaseConnection;
import static org.springframework.util.Assert.notNull;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.sql.DataSource;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.transaction.Transaction;
import org.springframework.jdbc.datasource.DataSourceUtils;

/**
 * 
 * @author duomn.hu
 * 
 * {@link org.mybatis.spring.transaction.SpringManagedTransaction}
 * 不再使用缓存的Conncetion，每次使用DataSourceUtils.getConnection(dataSource)获取连接<br>
 * 获取的Connection保存在Set中，避免同一个链接释放多次
 */
public class SpringManagedTransactionNewConn implements Transaction {
		
	  private final Log logger = LogFactory.getLog(getClass());

	  private final DataSource dataSource;

	  private Connection connection;
	  
	  /** 使用集合替换掉以前保存的值 */
	  private ConcurrentMap<Connection, ConnProp> map = new ConcurrentHashMap<Connection, SpringManagedTransactionNewConn.ConnProp>();


	  public SpringManagedTransactionNewConn(DataSource dataSource) {
	    notNull(dataSource, "No DataSource specified");
	    this.dataSource = dataSource;
	  }

	  /**
	   * {@inheritDoc}
	   */
	  public Connection getConnection() throws SQLException {
		openConnection();
	    return this.connection;
	  }

	  /**
	   * Gets a connection from Spring transaction manager and discovers if this
	   * {@code Transaction} should manage connection or let it to Spring.
	   * <p>
	   * It also reads autocommit setting because when using Spring Transaction MyBatis
	   * thinks that autocommit is always false and will always call commit/rollback
	   * so we need to no-op that calls.
	   */
	  private void openConnection() throws SQLException {
		  ConnProp connProp = new ConnProp();
		  connProp.conn = DataSourceUtils.getConnection(this.dataSource);
		  this.connection = connProp.conn;
		  
		  
		  if (map.putIfAbsent(connProp.conn, connProp) == null) {
			  connProp.autoCommit = connProp.conn.getAutoCommit();
			  connProp.isConnectionTransactional = isConnectionTransactional(connProp.conn, this.dataSource);
			  if (this.logger.isDebugEnabled()) {
				  this.logger.debug(
			          "JDBC Connection ["
			              + connProp.conn
			              + "] will"
			              + (connProp.isConnectionTransactional ? " " : " not ")
			              + "be managed by Spring");
			  }
		  }
  		}

	  /**
	   * {@inheritDoc}
	   */
	  public void commit() throws SQLException {
		  for (Entry<Connection, ConnProp> item : map.entrySet()) {
			  ConnProp connProp = item.getValue();
			  if (connProp.conn != null && !connProp.isConnectionTransactional && !connProp.autoCommit) {
				  if (this.logger.isDebugEnabled()) {
					  this.logger.debug("Committing JDBC Connection [" + item.getKey() + "]");
			      }
				  item.getKey().commit();
			  }
		  }
	  }

	  /**
	   * {@inheritDoc}
	   */
	  public void rollback() throws SQLException {
		  for (Entry<Connection, ConnProp> item : map.entrySet()) {
			  ConnProp connProp = item.getValue();
			  if (connProp.conn != null && !connProp.isConnectionTransactional && !connProp.autoCommit) {
				  if (this.logger.isDebugEnabled()) {
					  this.logger.debug("Rolling back JDBC Connection [" + connProp.conn + "]");
			      }
				  connProp.conn.rollback();
			  }
		  }
	  }

	  /**
	   * {@inheritDoc}
	   */
	  public void close() throws SQLException {
		  for (Entry<Connection, ConnProp> item : map.entrySet()) {
			  if (this.logger.isDebugEnabled()) {
				  this.logger.debug("releaseConnection JDBC Connection [" + item.getKey() + "]");
		      }
			  releaseConnection(item.getKey(), dataSource);
		  }
	  }
	  
	 private static class ConnProp {
		 private Connection conn;
		 private boolean autoCommit;
		 private boolean isConnectionTransactional;
	 }
}
